﻿//Copyright (c) 2009, Offbeat Guides
//All rights reserved.
//
//Redistribution and use in source and binary forms, with or without modification, are 
//permitted provided that the following conditions are met:
//
//* Redistributions of source code must retain the above copyright notice, this list of 
//conditions and the following disclaimer.
//
//* Redistributions in binary form must reproduce the above copyright notice, this list 
//of conditions and the following disclaimer in the documentation and/or other materials 
//provided with the distribution.
//
//* Neither the name of Offbeat Guides nor the names of its contributors may be used to 
//endorse or promote products derived from this software without specific prior written 
//permission.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
//EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
//OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
//SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
//SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
//OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
//HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 
//TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
//EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using Amazon.Services;
using System.Net;

namespace Amazon.Services.S3
{
    public class FileStream : System.IO.Stream
    {
        #region Variables
        private Amazon.Services.AmazonAccount _account;
        private Amazon.Services.ServiceClient _client;

        private System.String _bucket;
        private System.String _path;
        private System.IO.FileMode _mode;
        private System.IO.FileAccess _access;
        private System.IO.FileShare _share;

        private System.Boolean _useAsync;
        private System.IO.MemoryStream _stream;
        #endregion

        #region Overriden Properties
        public override bool CanRead { get { return this._access != FileAccess.Write; } }

        public override bool CanSeek { get { return true; } }

        public override bool CanWrite { get { return this._access != FileAccess.Read; } }

        public override Int64 Length { get { return (this._stream == null) ? 0 : this._stream.Length; } }

        public override Int64 Position
        {
            get { return (this._stream == null) ? -1 : this._stream.Position; }
            set { if (this._stream != null) this._stream.Position = value; }
        }
        #endregion

        #region Constructors and Finalizers
        public FileStream(AmazonAccount account, String path, FileMode mode)
            : this(account, path, mode, (mode == FileMode.Append) ? FileAccess.Write : FileAccess.ReadWrite, FileShare.Read, false) { }

        public FileStream(AmazonAccount account, String path, FileMode mode, FileAccess access)
            : this(account, path, mode, access, FileShare.Read, false) { }

        public FileStream(AmazonAccount account, String path, FileMode mode, FileAccess access, FileShare share)
            : this(account, path, mode, access, share, false) { }

        public FileStream(AmazonAccount account, String path, FileMode mode, FileAccess access, FileShare share, Boolean useAsync)
            : this(new ServiceClient(account, S3Configuration.Default), path, mode, access, share, useAsync) { }

        internal FileStream(ServiceClient client, String fullpath, FileMode mode, FileAccess access, FileShare share, Boolean useAsync)
        {
            if (client == null)
                throw new ArgumentNullException("client");
            if (String.IsNullOrEmpty(fullpath))
                throw new ArgumentNullException("fullpath");
            if (mode == FileMode.Append && access == FileAccess.Read)
                throw new InvalidOperationException("Cannot have a FileMode.Append when access is limited to FileAccess.Read.");

            Match match = FileSystemHelper.PathRegex.Match(fullpath);
            if (!match.Success)
                throw new ArgumentException(String.Format("Unable to locate the file specified. filename = \"{0}\".", fullpath));

            this._bucket = match.Groups["bucket"].Value;
            if (this._bucket.Length > DriveInfo.MaxNameLength || this._bucket.Length < DriveInfo.MinNameLength)
                throw new ArgumentException("Drive length is beyond the accepted length.");

            this._path = match.Groups["path"].Value.Replace("\\", "/");
            if (this._path.Length > DirectoryInfo.MaxPathLength || this._path.Length < DirectoryInfo.MinPathLength)
                throw new ArgumentException("Path length is beyond the accepted length.");

            this._account = client.Account;
            this._client = client;

            this._mode = mode;
            this._access = access;
            this._share = share;

            this._stream = new MemoryStream();
            this._useAsync = useAsync;

            this.InitializeStream();
        }

        internal FileStream(ServiceClient client, String bucket, String path, FileMode mode, FileAccess access, FileShare share, Boolean useAsync)
        {
            if (client == null)
                throw new ArgumentNullException("client");
            if (String.IsNullOrEmpty(bucket))
                throw new ArgumentNullException("bucket");
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("path");
            if (mode == FileMode.Append && access == FileAccess.Read)
                throw new InvalidOperationException("Cannot have a FileMode.Append when access is limited to FileAccess.Read.");

            this._bucket = bucket;
            if (this._bucket.Length > DriveInfo.MaxNameLength || this._bucket.Length < DriveInfo.MinNameLength)
                throw new ArgumentException("Drive length is beyond the accepted length.");

            this._path = path.Replace("\\", "/");
            if (this._path.Length > DirectoryInfo.MaxPathLength || this._path.Length < DirectoryInfo.MinPathLength)
                throw new ArgumentException("Path length is beyond the accepted length.");

            this._account = client.Account;
            this._client = client;
            this._stream = new MemoryStream();

            this._mode = mode;
            this._access = access;
            this._share = share;

            this._useAsync = useAsync;

            this.InitializeStream();
        }
        #endregion

        #region Methods
        private void InitializeStream()
        {
            Boolean exists = FileSystemHelper.FileExists(this._client, this._bucket, this._path);
            if ((this._mode == FileMode.Open || this._mode == FileMode.Truncate) && !exists)
                throw new FileNotFoundException(String.Format("Unable to locate file \"{0}:\\{1}\"", this._bucket, this._path));
            if (this._mode == FileMode.CreateNew && exists)
                throw new FileNotFoundException(String.Format("Located existing file \"{0}:\\{1}\"; Cannot create new file.", this._bucket, this._path));

            // if set to truncate, be sure to truncate the file
            if (this._mode == FileMode.Truncate || this._mode == FileMode.Create || this._mode == FileMode.CreateNew)
                FileSystemHelper.WriteFile(this._client, this._bucket, this._path, new Byte[0]);
            else if (this._mode == FileMode.Append || this._mode == FileMode.Open)
            {
                // append and open are the only modes that require an open
                Byte[] fileData = FileSystemHelper.ReadFile(this._client, this._bucket, this._path);
                this._stream = new MemoryStream(fileData);

                if (this._mode == FileMode.Append)
                    this._stream.Seek(fileData.Length, SeekOrigin.Begin);
            }
        }
        #endregion

        #region Overriden Methods
        public override void Close()
        {
            this._stream.Close();

            base.Close();
        }
        public override void Flush()
        {
            Byte[] buffer = this._stream.ToArray();

            FileSystemHelper.WriteFile(this._client, this._bucket, this._path, buffer);
        }

        public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count)
        {
            if (this._access == FileAccess.Write)
                throw new NotSupportedException();

            return this._stream.Read(buffer, offset, count);
        }

        public override Int64 Seek(Int64 offset, SeekOrigin origin)
        {
            return this._stream.Seek(offset, origin);
        }

        public override void SetLength(Int64 value)
        {
            this._stream.SetLength(value);
        }

        public override void Write(Byte[] buffer, Int32 offset, Int32 count)
        {
            if (this._access == FileAccess.Read)
                throw new NotSupportedException();

            this._stream.Write(buffer, offset, count);
        }
        #endregion
    }
}
